#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QDebug>
#include <QObject>
#include <QString>
#include <QPixmap>
#include <QLabel>
#include <QDir>
#include <QLineEdit>

void Qwidget_enable_test(Widget* Pthis){
    QPushButton* button = new QPushButton(Pthis);
    button->setText("按钮");

    //设置按钮为禁用
    button->setEnabled(false);
    //虽然 connect 是 QObject 的成员函数，它通常可以在继承了 QObject 的任何对象中直接使用
    //如果您的函数 Qwidget_test 位于全局命名空间或其他命名空间中，并且不在任何类的成员函数内部，则需要确保 connect 的调用上下文是正确的。
    button->connect(button, &QPushButton::clicked, Pthis, &Widget::handle);
}

void Qwidget_geometry_test(Widget* Pthis) {
    //Qt 中针对一些几何上的概念也进行了封装
    //QPoint 表示一个点
    //QRect 表示一个矩形
    //C++中使用上述对象，通常就会按照值的方式来传递参数了

    //geometry(), 获取到控件的位置和尺寸.返回结果是一个QRect, 包含了x,y, width, height.其x,y是左上角的坐标

    //setGeomety(QRect)
    //setGeometry(int x, int y,intwidth, int heigh)
    //设置控件的位置和尺寸.可以直接设置一个QRect,也可以分四个属性单独设置

    //move 只是修改位置，setGeometry既可以修改位置，又可以修改尺寸
}

void Qwidget_frame_test(Widget* Pthis) {
    //此处直接针对 widget 对象来使用 geometry 和 frameGeometry。观察区别
    //执行程序, 可以看到, 构造函数中, 打印出的 geometry 和 frameGeometry 是相同的
    //在构造方法中, Widget 刚刚创建出来, 还没有加入到对象树中. 此时也就不具备 Window frame
    Pthis->move(100, 100);
    QRect rect1 = Pthis->geometry();
    QRect rect2 = Pthis->frameGeometry();
    qDebug() << rect1;
    qDebug() << rect2;

//    window frame 的影响
//    如果 widget 作为一个窗口 (带有标题栏, 最小化, 最大化, 关闭按钮)
//    那么在计算尺寸和坐标的时候就有两种算法. 包含 window frame 和 不包含 window frame
//    其中 x(), y(), frameGeometry(), pos(), move() 都是按照包含 window frame 的方式来计算的
//    其中 geometry(), width(), height(), rect(), size() 则是按照不包含 window frame 的方式来计算的
//    当然, 如果一个不是作为窗口的widget , 上述两类方式得到的结果是一致的
}

void Qwidget_window_title_test(Widget* Pthis) {
    //当前windowsTitle属性，是从属于QWidgetQWidget是一个广泛的概念
    //windowTitle属性，只能针对顶层窗口这样的QWidget才有效
    Pthis->setWindowTitle("这是窗口标题");
    QString title = Pthis->windowTitle();
    qDebug() << title;

    //当前不应该给按钮设置windowTitle.但是实际设置了之后，没有任何效果，也没有报错此时“没有报错”这样的设定是不太科学的
    QPushButton* button = new QPushButton(Pthis);
    button->setText("按钮");
    button->move(100, 100);
    button->setWindowTitle("通过按钮设置窗口标题");
    title = Pthis->windowTitle();
    qDebug() << title;
}

void Qwidget_window_icon_test(Widget* Pthis) {
    //注意: Windows 下路径的分隔符可以使用 / 也可以使用 \ . 但是如果在 字符串 中使用 \ , 需要作转义字符的形式 \\ . 因此我们还是更推荐使用 /
    //我这种是原始字符串的写法
    //实际开发中, 我们一般不会在代码中通过绝对路径引入图片. 因为我们无法保证程序发布后, 用户的电脑上也有同样的路径
    //如果使用相对路径, 则需要确保代码中的相对路径写法和图片实际所在的路径匹配 (比如代码中写作 "./image/rose.jpg"
    //就需要在当前工作目录中创建 image 目录, 并把 rose.jpg 放进去)
    QIcon windowIcon(R"(G:\code\qt\build-Qwidget-Desktop_Qt_5_14_2_MinGW_64_bit-Debug\image\image.png)");

    Pthis->setWindowIcon(windowIcon);

    //直接在 Qt Creator 中执行程序, 可以看到当前工作目录是项目的构建目录
    //进入上述构建目录, 把里面的 exe 拷贝到其他目录中, 比如 D: 中. 再次执行程序, 可以看到当前工作目录已经发生改变
    //注意, 上述 构建目录, 是随时可删除的. 比如点击菜单栏中的 "构建" -> "清理项目" , 就会把这个目录中的内容清空掉
    //因此如果我们把图片文件放到构建目录中, 可能在不小心删除后就丢失了
    //我们还是希望能够把图片和源代码放到一起, 并且使我们的程序无论拷贝到任何位置中都能正确使用图片
    QLabel* label = new QLabel(Pthis);
    label->move(150, 150);
    QString cur_path = QDir::currentPath();
    label->setText(cur_path);

//    Qt 使用 qrc 机制帮我们自动完成了上述工作, 更方便的来管理项目依赖的静态资源
//    qrc 文件是一种XML格式的资源配置文件
//    它用XML记录硬盘上的文件和对应的随意指定的资源名称. 应用程序通过资源名称来访问这些资源
//    在Qt开发中, 可以通过将资源文件添加到项目中来方便地访问和管理这些资源
//    这些资源文件可以位于qrc文件所在目录的同级或其子目录下
//    在构建程序的过程中, Qt 会把资源文件的二进制数据转成 cpp 代码
//    编译到 exe 中. 从而使依赖的资源变得 "路径无关"
    //下面使用qrc机制来进行演示
    //添加的文件必须是在 qrc 文件的同级目录, 或者同级目录的子目录中
    QIcon windowIcon2(":/image/image2.png");
    //注意上述路径的访问规则.
    //使用 : 作为开头, 表示从 qrc 中读取资源.
    // / 是上面配置的前缀
    // image2.png 是资源的名称
    //需要确保代码中编写的路径和添加到 qrc 中资源的路径匹配. 否则资源无法被访问 (同时也不会有报错提示)
    //接下来, 我们可以进入到项目的构建目录, 可以看到, 目录中多了一个 qrc_image.cpp 文件
    //这些代码其实就是通过 unsigned char 数组, 把 rose.jpg 中的每个字节都记录下来
    //这些代码会被编译到 exe 中. 后续无论 exe 被复制到哪个目录下, 都确保能够访问到该图片资源
    Pthis->setWindowIcon(windowIcon2);

//    上述 qrc 这一套资源管理方案, 优点和缺点都很明显.
//    优点: 确保了图片, 字体, 声音等资源能够真正做到 "目录无关", 无论如何都不会出现资源丢失的情况
//    缺点: 不适合管理体积大的资源. 如果资源比较大 (比如是几个 MB 的文件), 或者资源特别多
//    生成的最终的 exe 体积就会比较大, 程序运行消耗的内存也会增大, 程序编译的时间也会显著增加
}

void Qwidget_window_opacity_test(Widget* Pthis) {

}

void Qwidget_cursor_test(Widget* Pthis) {
    //获取到当前 widget 的 cursor 属性, 返回 QCursor 对象
    //当鼠标悬停在该 widget 上时, 就会显示出对应的形状
    QCursor cursor(Qt::WaitCursor);
    //设置该widget 光标的形状. 仅在鼠标停留在该 widget 上时生效
    Pthis->setCursor(cursor);

    //通过自定义的图像来进行cursor的设置
    QPixmap pixmap(":/image/image.png");
    //进行图片大小的缩放
    pixmap = pixmap.scaled(25, 25);
    // 创建 QCursor 对象, 并指定 "热点" 为 (2, 2) 坐标位置
    // 所谓 "热点" 就是鼠标点击时生效的位置
    QCursor cursor2(pixmap, 0, 0);
    Pthis->setCursor(cursor2);
}

void Qwidget_font_test(Widget* Pthis) {
    QLabel* label = new QLabel(Pthis);
    label->setText("测试文本");
    label->move(100, 500);

    //创建字体对象
    QFont font;
    font.setFamily("仿宋");
    //字体大小
    font.setPixelSize(50);
    //是否加粗
    font.setBold(true);
    //是否倾斜
    font.setItalic(true);
    font.setUnderline(true);
    //是否带有删除线
    font.setStrikeOut(true);

    label->setFont(font);
}

void Qwidget_toolTip_test(Ui::Widget* ui) {
    //设置 toolTip.鼠标悬停在该 widget 上时会有提示说明.
    ui->pushButton_yes->setToolTip("这是一个 yes 按钮");
    //设置 toolTip 提示的时间 单位 ms 时间到后 toolTip 自动消失
    ui->pushButton_yes->setToolTipDuration(3000);

    ui->pushButton_no->setToolTip("这是一个 no 按钮");
    ui->pushButton_no->setToolTipDuration(7000);
}

void Qwidget_focusPolicy_test(Widget* Pthis) {
    //Qt::NoFocus, 控件不会接收键盘焦点
    QLineEdit* line_edit1 = new QLineEdit(Pthis);
    line_edit1->move(400, 400);
    line_edit1->setFocusPolicy(Qt::NoFocus);

    //Qt::TabFocus, 控件可以通过Tab键接收焦点
    QLineEdit* line_edit2 = new QLineEdit(Pthis);
    line_edit2->move(400, 450);
    line_edit2->setFocusPolicy(Qt::TabFocus);

    //Qt::ClickFocus, 控件在鼠标点击时接收焦点
    QLineEdit* line_edit3 = new QLineEdit(Pthis);
    line_edit3->move(400, 500);
    line_edit3->setFocusPolicy(Qt::ClickFocus);

    //Qt::StrongFocus, 控件可以通过Tab键和鼠标点击接收焦点(默认值)
    QLineEdit* line_edit4 = new QLineEdit(Pthis);
    line_edit4->move(400, 550);
    line_edit4->setFocusPolicy(Qt::StrongFocus);
}

void Qwidget_styleSheet_test(Widget* Pthis) {

}

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    //Qwidget_enable_test(this);
    //Qwidget_geometry_test(this);
    //Qwidget_frame_test(this);
    //Qwidget_window_title_test(this);
    Qwidget_window_icon_test(this);
    //Qwidget_window_opacity_test(this);
    Qwidget_cursor_test(this);
    Qwidget_font_test(this);
    //Qwidget_toolTip_test(ui);
    //Qwidget_focusPolicy_test(this);
    Qwidget_styleSheet_test(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::handle()
{
    qDebug() << "handle";
}


void Widget::on_pushButton_clicked()
{
    qDebug() << "执行了槽函数";
}

void Widget::on_pushButton_enable_clicked()
{
    //切换第一个按钮的禁用状态
    //通过获取第一个按钮的可用状态来进行状态的切换
    //enabled描述了一个控件是否处于“可用”状态
    //所谓“禁用”指的是该控件不能接收任何用户的输入事件，并且外观上往往是灰色的
    //如果一个widget被禁用，则该widget的子元素也被禁用.
    //isEnabled,获取到控件的可用状态
    bool enable = ui->pushButton->isEnabled();
    if (enable) {
        //setEnabled,设置控件是否可使用,true表示可用，false表示禁用.
        ui->pushButton->setEnabled(false);
    }
    else {
        ui->pushButton->setEnabled(true);
    }
}

void Widget::on_pushButton_up_clicked()
{
    //获取到文本框本身的geometry
    QRect rect = ui->lineEdit->geometry();
    qDebug() << rect;
    //通过这种方式方式进行位置的设置会导致文本框本身也受到改变
    //上述代码中我们是直接设置的 QRect 中的 x, y
    //实际上 QRect 内部是存储了左上和右下两个点的坐标, 再通过这两个点的坐标差值计算长宽
    //单纯修改左上坐标就会引起整个矩形的长宽发生改变
//    rect.setY(rect.y() - 5);
//    ui->lineEdit->setGeometry(rect);

    //可以通过单独的设置来进行位置的设置
    ui->lineEdit->setGeometry(rect.x(), rect.y() - 5, rect.width(), rect.height());
}

void Widget::on_pushButton_down_clicked()
{
    QRect rect = ui->lineEdit->geometry();
    qDebug() << rect;
//    rect.setY(rect.y() + 5);
//    ui->lineEdit->setGeometry(rect);

    ui->lineEdit->setGeometry(rect.x(), rect.y() + 5, rect.width(), rect.height());
}

void Widget::on_pushButton_left_clicked()
{
    QRect rect = ui->lineEdit->geometry();
    qDebug() << rect;
//    rect.setX(rect.x() - 5);
//    ui->lineEdit->setGeometry(rect);

    ui->lineEdit->setGeometry(rect.x() - 5, rect.y(), rect.width(), rect.height());
}

void Widget::on_pushButton_right_clicked()
{
    QRect rect = ui->lineEdit->geometry();
    qDebug() << rect;
//    rect.setX(rect.x() + 5);
//    ui->lineEdit->setGeometry(rect);

    ui->lineEdit->setGeometry(rect.x() + 5, rect.y(), rect.width(), rect.height());
}

//clicked代表鼠标按下并且松开的时候触发
void Widget::on_pushButton_accept_clicked()
{
    ui->pushButton_accept->setText("成功借到500!");
}

//pressed代表鼠标按下的时候触发
void Widget::on_pushButton_reject_pressed()
{
    //可以通过生成随机数的方式，来确定按钮的新的位置

    //获取当前程序窗口的尺寸
    int width = this->geometry().width();
    int height = this->geometry().height();

    //重新生成按钮的位置
    int x = rand() % width;
    int y = rand() % height;

    ui->pushButton_reject->move(x, y);
}

void Widget::on_pushButton_get_frame_clicked()
{
    //在点击按钮时, 打印的 geometry 和 frameGeometry 则存在差异
    //在按钮的 slot 函数中, 由于用户点击的时候, 对象树已经构造好了, 此时 Widget已经具备了Window frame, 因此在位置和尺寸上均出现了差异
    this->move(100, 100);
    //通过这里可以看出move是针对于frameGeometry来进行移动的
    QRect rect1 = this->geometry();
    QRect rect2 = this->frameGeometry();
    //对于大多数控件（如按钮），如果它们没有明显的边框或外延部分，frameGeometry() 和 geometry() 返回的值可能相同或非常接近。甚至就是相同的
    //QRect rect1 = ui->pushButton_get_frame->geometry();
    //QRect rect2 = ui->pushButton_get_frame->frameGeometry();
    qDebug() << rect1;
    qDebug() << rect2;
}

void Widget::on_pushButton_opacity_down_clicked()
{
    //获取到控件的不透明数值. 返回 float, 取值为 0.0 -> 1.0 其中 0.0 表示全透明, 1.0 表示完全不透明
    double opacity = this->windowOpacity();
    if (opacity <= 0) {
        return;
    }
    qDebug() << opacity;
    //注意, C++ 中 float 类型遵守 IEEE 754 标准, 因此在进行运算的时候会有一定的精度误差. 因此 1 - 0.1 的数值并非是 0.9
    opacity -= 0.1;
    //设置控件的不透明数值
    this->setWindowOpacity(opacity);
}

void Widget::on_pushButton_opacity_up_clicked()
{
    double opacity = this->windowOpacity();
    if (opacity >= 1.0) {
        return;
    }

    qDebug() << opacity;
    opacity += 0.1;
    this->setWindowOpacity(opacity);

}

//代码示例: 实现切换夜间模式
void Widget::on_pushButton_day_clicked()
{
    //设置窗口的样式
    this->setStyleSheet("background-color: rgb(240, 240, 240);");

    //设置按钮的样式
    ui->pushButton_day->setStyleSheet("color: black;");
    ui->pushButton_night->setStyleSheet("color: black;");
}

void Widget::on_pushButton_night_clicked()
{
    //设置窗口的样式
    this->setStyleSheet("background-color: black");

    //设置按钮的样式
    ui->pushButton_day->setStyleSheet("color: white;");
    ui->pushButton_night->setStyleSheet("color: white;");
}
